﻿// Copyright 2012 Stuart Turner
// Licensed under the Apache License, Version 2.0 [http://www.apache.org/licenses/LICENSE-2.0]

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace Complete.Data.Internal
{
	internal static class DataReaderFunctions
	{
		public static readonly ParameterExpression DATA_READER_PARAMETER = Expression.Parameter(typeof(DbDataReader), "idr");

		private static readonly MethodInfo getBoolean =
			typeof(IDataRecord).GetMethod("GetBoolean");
		private static readonly MethodInfo getByte =
			typeof(IDataRecord).GetMethod("GetByte");
		private static readonly MethodInfo getChar =
			typeof(IDataRecord).GetMethod("GetChar");
		private static readonly MethodInfo getDateTime =
			typeof(IDataRecord).GetMethod("GetDateTime");
		private static readonly MethodInfo getDecimal =
			typeof(IDataRecord).GetMethod("GetDecimal");
		private static readonly MethodInfo getFloat =
			typeof(IDataRecord).GetMethod("GetFloat");
		private static readonly MethodInfo getDouble =
			typeof(IDataRecord).GetMethod("GetDouble");
		private static readonly MethodInfo getGUID =
			typeof(IDataRecord).GetMethod("GetGuid");
		private static readonly MethodInfo getInt16 =
			typeof(IDataRecord).GetMethod("GetInt16");
		private static readonly MethodInfo getInt32 =
			typeof(IDataRecord).GetMethod("GetInt32");
		private static readonly MethodInfo getInt64 =
			typeof(IDataRecord).GetMethod("GetInt64");
		private static readonly MethodInfo getString =
			typeof(IDataRecord).GetMethod("GetString");
		private static readonly MethodInfo getValue =
			typeof(IDataRecord).GetMethod("GetValue");
		private static readonly MethodInfo getFieldType =
			typeof(IDataRecord).GetMethod("GetFieldType");
		private static readonly MethodInfo isDBNull =
			typeof(IDataRecord).GetMethod("IsDBNull");

		public static Expression BuildDataReaderExpression(int column, Type type)
		{
			var isNullable = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
			var isString = type == typeof(string);
			var baseType = isNullable ? type.GetGenericArguments()[0] : type;

			Expression expression =
				Expression.Call(
					DATA_READER_PARAMETER,
					GetDataReaderMethod(baseType),
					Expression.Constant(column));

			if (baseType == typeof(bool))
			{
				expression =
					Expression.Condition(
						Expression.Equal(
							Expression.Call(
								DATA_READER_PARAMETER,
								getFieldType,
								Expression.Constant(column)),
							Expression.Constant(typeof(bool))),
						Expression.Call(
							DATA_READER_PARAMETER,
							GetDataReaderMethod(typeof(bool)),
							Expression.Constant(column)),
						Expression.Condition(
							Expression.Equal(
								Expression.Call(
									DATA_READER_PARAMETER,
									GetDataReaderMethod(typeof(int)),
									Expression.Constant(column)),
								Expression.Constant(0)),
							Expression.Constant(false),
							Expression.Constant(true)));
			}

			if (isNullable || isString)
				expression =
					Expression.Condition(
						Expression.Call(
							DATA_READER_PARAMETER,
							isDBNull,
							Expression.Constant(column)),
						Expression.Constant(null, type),
						Expression.Convert(expression, type));
			else if (type.IsEnum)
				expression =
					Expression.Convert(expression, type);
			else if (type == typeof(Complete.Data.Linq.Binary))
				// return of GetValue will be byte[], but casted to object
				// First convert object to byte[], 
				// then convert again to use implicit operator to convert
				// byte[] to Binary
				expression =
					Expression.Condition(
						Expression.Call(
							DATA_READER_PARAMETER,
							isDBNull,
							Expression.Constant(column)),
						Expression.Constant(null, type),
						Expression.Convert(Expression.Convert(expression, typeof(byte[])), type));

			return expression;
		}

		private static MethodInfo GetDataReaderMethod(Type type)
		{

			switch (Type.GetTypeCode(type))
			{
				case TypeCode.Boolean:
					return getBoolean;
				case TypeCode.Byte:
					return getByte;
				case TypeCode.Char:
					return getChar;
				case TypeCode.DateTime:
					return getDateTime;
				case TypeCode.Decimal:
					return getDecimal;
				case TypeCode.Single:
					return getFloat;
				case TypeCode.Double:
					return getDouble;
				case TypeCode.Int16:
					return getInt16;
				case TypeCode.Int32:
					return getInt32;
				case TypeCode.Int64:
					return getInt64;
				case TypeCode.String:
					return getString;
				default:
				case TypeCode.Object:
					if (type == typeof(Complete.Data.Linq.Binary)) return getValue;
					if (type == typeof(byte[])) return getValue;
					throw new NotSupportedException("Unexpected DB type. Please verify.");
			}
		}

	}
}
